﻿using System;
using System.Runtime.CompilerServices;

namespace FixedMath
{
    public partial struct Fix64
    {
        public static readonly Fix64 Epsilon = One / 1000;
        public static readonly Fix64 PositiveInfinity = MaxValue;
        public static readonly Fix64 NegativeInfinity = MinValue;
        public static readonly Fix64 NaN = MinValue;

        public static readonly Fix64 _1E_3 = 0.001f;
        public static readonly Fix64 _1E_6 = 0.000001;
        public static readonly Fix64 _1E_06 = new Fix64(1);
        public static readonly Fix64 _1E_16 = 0.000000001;

        public static bool IsNaN(Fix64 f)
        {
            return false;
        }

        public static bool IsInfinity(Fix64 f)
        {
            return false;
        }

        public static bool IsNegativeInfinity(Fix64 f)
        {
            return false;
        }

        public static bool IsPositiveInfinity(Fix64 f)
        {
            return false;
        }

        public static bool IsNormal(Fix64 f)
        {
            long value = f.RawValue;
            return value != 0;
        }

        /// <summary>
        /// Adds x and y witout performing overflow checking. Should be inlined by the CLR.
        /// </summary>
        public static Fix64 FastAdd(Fix64 x, Fix64 y)
        {
            return new Fix64(x.RawValue + y.RawValue);
        }

        /// <summary>
        /// Subtracts y from x witout performing overflow checking. Should be inlined by the CLR.
        /// </summary>
        public static Fix64 FastSubtract(Fix64 x, Fix64 y)
        {
            return new Fix64(x.RawValue - y.RawValue);
        }

        /// <summary>
        /// Performs multiplication without checking for overflow.
        /// Useful for performance-critical code where the values are guaranteed not to cause overflow
        /// </summary>
        public static Fix64 FastMultiply(Fix64 x, Fix64 y)
        {
            var xl = x.RawValue;
            var yl = y.RawValue;

            var xlo = (ulong)(xl & 0x00000000FFFFFFFF);
            var xhi = xl >> FRACTIONAL_PLACES;
            var ylo = (ulong)(yl & 0x00000000FFFFFFFF);
            var yhi = yl >> FRACTIONAL_PLACES;

            var lolo = xlo * ylo;
            var lohi = (long)xlo * yhi;
            var hilo = xhi * (long)ylo;
            var hihi = xhi * yhi;

            var loResult = lolo >> FRACTIONAL_PLACES;
            var midResult1 = lohi;
            var midResult2 = hilo;
            var hiResult = hihi << FRACTIONAL_PLACES;

            var sum = (long)loResult + midResult1 + midResult2 + hiResult;
            return new Fix64(sum);
        }

        /// <summary>
        /// Performs modulo as fast as possible; throws if x == MinValue and y == -1.
        /// Use the operator (%) for a more reliable but slower modulo.
        /// </summary>
        public static Fix64 FastRemainder(Fix64 x, Fix64 y)
        {
            return new Fix64(x.RawValue % y.RawValue);
        }

        /// <summary>
        /// Multiplies two FP values together.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Fix64 Mul(Fix64 a, Fix64 b)
        {
            long ai = a.RawValue >> 32;
            long af = a.RawValue & FractionMask;
            long bi = b.RawValue >> 32;
            long bf = b.RawValue & FractionMask;
            if (ai * bi > 2147483647L)
                return MaxValue;
            if (ai * bi < -2147483648L)
                return MinValue;
            long result = (long)((ulong)(af * bf) >> 32) + ai * b.RawValue + af * bi;
            return new Fix64(result);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Fix64 MulDefault(Fix64 x, Fix64 y)
        {
            var xl = x.RawValue;
            var yl = y.RawValue;

            var xlo = (ulong)(xl & 0x00000000FFFFFFFFL);
            var xhi = xl >> FRACTIONAL_PLACES;
            var ylo = (ulong)(yl & 0x00000000FFFFFFFFL);
            var yhi = yl >> FRACTIONAL_PLACES;

            var lolo = xlo * ylo;
            var lohi = (long)xlo * yhi;
            var hilo = xhi * (long)ylo;
            var hihi = xhi * yhi;

            var loResult = lolo >> FRACTIONAL_PLACES;
            var midResult1 = lohi;
            var midResult2 = hilo;
            var hiResult = hihi << FRACTIONAL_PLACES;

            bool overflow = false;

            long x1 = (long)loResult;
            long y1 = midResult1;
            var sum = x1 + y1;
            // x + y overflows if sign(x) ^ sign(y) != sign(sum)
            overflow |= ((x1 ^ y1 ^ sum) & MIN_VALUE) != 0;

            x1 = sum;
            y1 = midResult2;
            sum = x1 + y1;
            // x + y overflows if sign(x) ^ sign(y) != sign(sum)
            overflow |= ((x1 ^ y1 ^ sum) & MIN_VALUE) != 0;

            x1 = sum;
            y1 = hiResult;
            sum = x1 + y1;
            // x + y overflows if sign(x) ^ sign(y) != sign(sum)
            overflow |= ((x1 ^ y1 ^ sum) & MIN_VALUE) != 0;

            //var sum = AddOverflowHelper((long)loResult, midResult1, ref overflow);
            //sum = AddOverflowHelper(sum, midResult2, ref overflow);
            //sum = AddOverflowHelper(sum, hiResult, ref overflow);

            bool opSignsEqual = ((xl ^ yl) & MIN_VALUE) == 0;

            // if signs of operands are equal and sign of result is negative,
            // then multiplication overflowed positively
            // the reverse is also true
            if (opSignsEqual)
            {
                if (sum < 0 || (overflow && xl > 0))
                {
                    return MaxValue;
                }
            }
            else
            {
                if (sum > 0)
                {
                    return MinValue;
                }
            }

            // if the top 32 bits of hihi (unused in the result) are neither all 0s or 1s,
            // then this means the result overflowed.
            var topCarry = hihi >> FRACTIONAL_PLACES;
            if (topCarry != 0 && topCarry != -1 /*&& xl != -17 && yl != -17*/)
            {
                return opSignsEqual ? MaxValue : MinValue;
            }

            // If signs differ, both operands' magnitudes are greater than 1,
            // and the result is greater than the negative operand, then there was negative overflow.
            if (!opSignsEqual)
            {
                long posOp, negOp;
                if (xl > yl)
                {
                    posOp = xl;
                    negOp = yl;
                }
                else
                {
                    posOp = yl;
                    negOp = xl;
                }
                if (sum > negOp && negOp < -ONE && posOp > ONE)
                {
                    return MinValue;
                }
            }

            return new Fix64(sum);
        }
    }
}
